home *** CD-ROM | disk | FTP | other *** search
- /* POP3 Server state machine - see RFC 1460
- *
- * Jan 92 Erik Olson olson@phys.washington.edu
- * Taken from POP2 server code in NOS 910618
- * Rewritten/converted to POP3
- * Feb 92 William Allen Simpson
- * integrated with current work
- * Aug 94 ported to WNOS (DG1ZX)
- *
- * "Need-to" list: XTND XMIT (to get WinQVTnet to work)
- */
-
- #include <stdio.h>
- #include <fcntl.h>
- #include <time.h>
- #include <sys/stat.h>
- #ifdef UNIX
- #include <sys/types.h>
- #endif
- #if defined(__STDC__) || defined(__TURBOC__)
- #include <stdarg.h>
- #endif
- #include <ctype.h>
- #include <setjmp.h>
-
- #include "global.h"
- #include "config.h"
-
- #ifdef POP3_SERVER
-
- #include "mbuf.h"
- #include "cmdparse.h"
- #include "socket.h"
- #include "proc.h"
- #include "files.h"
- #include "smtp.h"
- #include "dirutil.h"
-
-
- #define LOG 1
- #define BUF_LEN 1024
-
- /* ---------------- common server data structures ---------------- */
- /* POP message pointer element */
-
- struct pop_msg {
- long len;
- long pos;
- int deleted;
- struct pop_msg *next;
- };
-
- /* POP server control block */
-
- struct pop_scb {
- int socket; /* socket number for this connection */
- char state; /* server state */
- #define LSTN 0
- #define AUTH 1
- #define TRANS 2
- #define UPDATE 3
- #define DONE 5
- char buf[BUF_LEN]; /* input line buffer */
- char count; /* line buffer length */
- char username[64]; /* user/folder name */
- FILE *wf; /* work folder file pointer */
- int folder_len; /* number of msgs in current folder */
- int high_num; /* highest message number accessed */
- long folder_file_size; /* length of the current folder file, in bytes */
- char folder_modified; /* mail folder contents modified flag */
- struct pop_msg *msg; /* message database link-list */
- };
-
- #define NULLSCB (struct pop_scb *)0
-
- /* Response messages */
-
- static char count_rsp[] = "+OK you have %d messages\n",
- error_rsp[] = "-ERR %s\n",
- greeting_msg[] = "+OK %s POP3 ready\n",
- user_rsp[] = "+OK user\n",
- /* pass_rsp[] = "+OK password\r\n", */
- stat_rsp[] = "+OK %d %ld\n",
- list_single_rsp[] = "+OK %d %ld\n",
- list_multi_rsp[] = "+OK %d messages (%ld octets)\n",
- retr_rsp[] = "+OK %ld octets\n",
- multi_end_rsp[] = ".\n",
- dele_rsp[] = "+OK message %d deleted\n",
- noop_rsp[] = "+OK\n",
- last_rsp[] = "+OK %d\n",
- signoff_msg[] = "+OK %s POP3 server signing off\n";
-
-
- static void near delete_scb __ARGS((struct pop_scb *scb));
- static void pop3serv __ARGS((int s,void *unused,void *p));
- static int near poplogin __ARGS((char *pass,char *username));
- static void near pop3_sm __ARGS((struct pop_scb *scb));
-
- static int Spop = -1; /* prototype socket for service */
-
-
- /* Start up POP3 receiver service */
- int
- pop3start(int argc,char *argv[],void *p) {
- struct sockaddr_in lsocket;
- int s;
-
- if (Spop != -1) {
- return 0;
- }
-
- psignal(Curproc,0); /* Don't keep the parser waiting */
- chname(Curproc,"POP3 listener");
-
- lsocket.sin_family = AF_INET;
- lsocket.sin_addr.s_addr = INADDR_ANY;
- lsocket.sin_port = (argc < 2) ? IPPORT_POP3 : atoi(argv[1]);
-
- Spop = socket(AF_INET,SOCK_STREAM,0);
- bind(Spop,(char *)&lsocket,sizeof(lsocket));
-
- listen(Spop,1);
-
- for (;;) {
- if((s = accept(Spop,NULLCHAR,(int *)NULL)) == -1)
- break; /* Service is shutting down */
-
- if (availmem() < Memthresh) {
- usputs(s,Nospace);
- shutdown(s,1);
- } else {
- /* Spawn a server */
- sockmode(s,SOCK_ASCII);
- newproc("POP3 server",2048,pop3serv,s,NULL,NULL,0);
- }
- }
- return 0;
- }
-
- /* Shutdown POP3 service (existing connections are allowed to finish) */
- int
- pop3stop(int argc,char *argv[],void *p) {
- close_s(Spop);
- Spop = -1;
- return 0;
- }
-
- static void
- pop3serv(int s,void *unused,void *p) {
- struct pop_scb *scb;
-
- sockowner(s,Curproc); /* We own it now */
- log(s,"open POP3");
-
- if((scb = (struct pop_scb *)mxallocw(sizeof(struct pop_scb))) == NULLSCB) {
- tputs(Nospace);
- #ifdef LOG
- log(scb->socket,"close POP3- no space");
- #endif
- close_s(s);
- return;
- }
-
- scb->username[0] = '\0';
- scb->msg = NULL;
- scb->wf = NULL;
- scb->count = scb->folder_file_size = 0;
- scb->folder_modified = FALSE;
- scb->socket = s;
- scb->state = AUTH;
-
- usprintf(scb->socket,greeting_msg,Hostname);
-
- for (;;) {
- if ( scb->state == DONE
- || (scb->count = recvline(s,scb->buf,BUF_LEN)) == -1){
- /* He closed on us */
- break;
- }
- rip(scb->buf);
- if (strlen(scb->buf) == 0) /* Ignore blank cmd lines */
- continue;
- pop3_sm(scb);
- }
-
- log(scb->socket,"close POP3");
- close_s(scb->socket);
- delete_scb(scb);
- }
-
-
- /* Free msg link-list */
- static void near
- delete_msglist(struct pop_msg *b_msg) {
- struct pop_msg *msg,*msg2;
-
- msg = b_msg;
- while(msg!=NULL) {
- msg2=msg->next;
- xfree(msg);
- msg=msg2;
- }
- }
-
- /* Free resources, delete control block */
- static void near
- delete_scb(struct pop_scb *scb) {
- if (scb == NULLSCB)
- return;
- if (scb->wf != NULL)
- fclose(scb->wf);
- if (scb->msg != NULL)
- delete_msglist(scb->msg);
- xfree((char *)scb);
- }
-
-
- /* --------------------- start of POP server code ------------------------ */
-
- #define BITS_PER_WORD 16
-
- #define isSOM(x) ((strncmp(x,"From ",5) == 0))
-
- /* Command string specifications */
-
- static char
- user_cmd[] = "USER ",
- pass_cmd[] = "PASS ",
- quit_cmd[] = "QUIT",
- stat_cmd[] = "STAT",
- list_cmd[] = "LIST",
- retr_cmd[] = "RETR",
- dele_cmd[] = "DELE",
- noop_cmd[] = "NOOP",
- rset_cmd[] = "RSET",
- top_cmd[] = "TOP",
- last_cmd[] = "LAST";
-
- static void near
- pop3_sm(struct pop_scb *scb) {
- char password[40];
-
- /* some prototypes */
- void near state_error(struct pop_scb *,char *);
- void near fatal_error(struct pop_scb *,char *);
- void near open_folder(struct pop_scb *);
- void near do_cleanup(struct pop_scb *);
- void near stat_message(struct pop_scb *);
- void near list_message(struct pop_scb *);
- void near retr_message(struct pop_scb *);
- void near dele_message(struct pop_scb *);
- void near noop_message(struct pop_scb *);
- void near last_message(struct pop_scb *);
- void near rset_message(struct pop_scb *);
- void near top_message(struct pop_scb *);
- void near close_folder(struct pop_scb *);
-
- if (scb == NULLSCB) /* be certain it is good -- wa6smn */
- return;
-
- switch(scb->state) {
- case AUTH:
- if (strncmp(scb->buf,user_cmd,strlen(user_cmd)) == 0){
- char *cp1, *cp = scb->buf;
-
- while (*cp++ != ' '); /* skip spaces */
- cp1 = cp;
- while (*cp++ != ' ');
- *cp = '\0';
- strcpy(scb->username,cp1);
- usputs(scb->socket,user_rsp);
-
- } else if (strncmp(scb->buf,pass_cmd,strlen(pass_cmd)) == 0){
- char *cp1, *cp = scb->buf;
-
- while (*cp++ != ' '); /* skip spaces */
- cp1 = cp;
- while (*cp++ != ' ');
- *cp = '\0';
- strcpy(password,cp1);
- if (!poplogin(scb->username,password)) {
- #ifdef LOG
- log(scb->socket,"POP3 access DENIED to %s",scb->username);
- #endif
- state_error(scb,"Access DENIED!!");
- return;
- }
-
- #ifdef LOG
- log(scb->socket,"POP3 access granted to %s",scb->username);
- #endif
- open_folder(scb);
- } else if (strncmp(scb->buf,quit_cmd,strlen(quit_cmd)) == 0){
- do_cleanup(scb);
- } else
- state_error(scb,"(AUTH) expected USER, PASS or QUIT");
- break;
-
- case TRANS:
- if (strncmp(scb->buf,stat_cmd,strlen(stat_cmd)) == 0)
- stat_message(scb);
-
- else if (strncmp(scb->buf,list_cmd,strlen(list_cmd)) == 0)
- list_message(scb);
-
- else if (strncmp(scb->buf,retr_cmd,strlen(retr_cmd)) == 0)
- retr_message(scb);
-
- else if (strncmp(scb->buf,dele_cmd,strlen(dele_cmd)) == 0)
- dele_message(scb);
-
- else if (strncmp(scb->buf,last_cmd,strlen(noop_cmd)) == 0)
- noop_message(scb);
-
- else if (strncmp(scb->buf,last_cmd,strlen(last_cmd)) == 0)
- last_message(scb);
-
- else if (strncmp(scb->buf,top_cmd,strlen(top_cmd)) == 0)
- top_message(scb);
-
- else if (strncmp(scb->buf,rset_cmd,strlen(rset_cmd)) == 0)
- rset_message(scb);
-
- else if (strncmp(scb->buf,quit_cmd,strlen(quit_cmd)) == 0)
- do_cleanup(scb);
-
- else
- state_error(scb,"(TRANS) unsupported/unrecognized command");
- break;
-
- case DONE:
- break;
-
- default:
- fatal_error(scb,"(TOP) State Error!!");
- break;
- }
- }
-
- static void near
- do_cleanup(struct pop_scb *scb) {
- void near close_folder(struct pop_scb *);
-
- close_folder(scb);
- usprintf(scb->socket,signoff_msg,Hostname);
- scb->state = DONE;
- }
-
- static void near
- state_error(struct pop_scb *scb,char *msg) {
- usprintf(scb->socket,error_rsp,msg);
- }
-
- static void near
- fatal_error(struct pop_scb *scb,char *msg) {
- usprintf(scb->socket,error_rsp,msg);
- scb->state = DONE;
- }
-
- static void near
- close_folder(struct pop_scb *scb) {
- char folder_pathname[MAXPATH];
- char line[BUF_LEN];
- FILE *fd;
- int deleted = FALSE;
- int msg_no = 0;
- struct pop_msg *msg;
- struct stat folder_stat;
- int near newmail(struct pop_scb *);
- void near state_error(struct pop_scb *,char *);
- void near fatal_error(struct pop_scb *,char *);
-
-
- if (scb->wf == NULL)
- return;
-
- if (!scb->folder_modified) {
- /* no need to re-write the folder if we have not modified it */
- fclose(scb->wf);
- scb->wf = NULL;
- delete_msglist(scb->msg);
- scb->msg=NULL;
- return;
- }
-
- sprintf(folder_pathname,"%s/%s.txt",Mailspool,scb->username);
-
- if (newmail(scb)) {
- /* copy new mail into the work file and save the
- message count for later */
- if ((fd = open_file(folder_pathname,"r",0,1)) == NULLFILE) {
- fatal_error(scb,"Unable to add new mail to folder");
- return;
- }
-
- fseek(scb->wf,0,SEEK_END);
- fseek(fd,scb->folder_file_size,SEEK_SET);
- while (!feof(fd)) {
- fgets(line,BUF_LEN,fd);
- fputs(line,scb->wf);
- pwait(NULL);
- }
- fclose(fd);
- }
-
- /* now create the updated mail folder */
-
- if ((fd = open_file(folder_pathname,"w",0,1)) == NULLFILE){
- fatal_error(scb,"Unable to update mail folder");
- return;
- }
-
- rewind(scb->wf);
- msg=scb->msg;
- while (!feof(scb->wf)){
- fgets(line,BUF_LEN,scb->wf);
-
- if (isSOM(line)){
- if (msg!=NULL)
- msg=msg->next;
- msg_no++;
- if (msg!=NULL)
- deleted = msg->deleted;
- else
- deleted = FALSE;
- }
-
- if (deleted)
- continue;
- fputs(line,fd);
- }
-
- fclose(fd);
-
- if (!stat(folder_pathname,&folder_stat)) {
- if (folder_stat.st_size == 0L)
- unlink(folder_pathname);
- }
-
- fclose(scb->wf);
- scb->wf = NULL;
- delete_msglist(scb->msg);
- scb->msg=NULL;
-
- }
-
- static void near
- open_folder(struct pop_scb *scb) {
- char folder_pathname[MAXPATH];
- char *cp, line[BUF_LEN];
- long pos;
- FILE *fd;
- struct pop_msg *msg;
- struct stat folder_stat;
-
- sprintf(folder_pathname,"%s/%s.txt",Mailspool,scb->username);
- scb->folder_len = 0;
- scb->folder_file_size = 0;
-
- if (stat(folder_pathname,&folder_stat)){
- usprintf(scb->socket,count_rsp,scb->folder_len);
- scb->state = TRANS;
- return; /* no file = OK */
- }
-
- scb->folder_file_size = folder_stat.st_size;
-
- if ((fd = open_file(folder_pathname,"r",0,1)) == NULLFILE){
- state_error(scb,"Unable to open mail folder");
- return;
- }
-
- if ((scb->wf = temp_file(0,1)) == NULL) {
- state_error(scb,"Unable to create work folder");
- fclose(fd);
- return;
- }
-
- /* create first element */
- if ( (scb->msg=(struct pop_msg *)cxallocw(sizeof(struct pop_msg),1)) == NULL) {
- fatal_error(scb,"Unable to create pointer list");
- return;
- }
-
- scb->msg->next=NULL;
- msg=scb->msg;
- msg->len=0;
- msg->deleted=0;
-
- while(!feof(fd)) {
- pos=ftell(scb->wf);
- fgets(line,BUF_LEN,fd);
-
- /* scan for begining of a message */
- if (isSOM(line)) {
- scb->folder_len++;
-
- if( (msg->next=(struct pop_msg *)cxallocw(sizeof(struct pop_msg),1)) == NULL) {
- fatal_error(scb,"Unable to create pointer list");
- return;
- }
-
- msg=msg->next;
- msg->pos=pos;
- msg->next=NULL;
- msg->len=0;
- msg->deleted=0;
- pwait(NULL);
-
- /* now put the line in the work file */
- }
- fputs(line,scb->wf);
- if((cp = strpbrk(line,"\r\n")) != NULLCHAR)
- *cp = '\0';
- if ( *line == '.' )
- msg->len++;
- msg->len +=strlen(line)+2; /* Add msg len count */
- }
-
- fclose(fd);
- scb->high_num=0; /* reset high read */
-
- usprintf(scb->socket,count_rsp,scb->folder_len);
- scb->state = TRANS;
- }
-
- static void near
- stat_message(struct pop_scb *scb) {
- long total=0;
- int count=0;
- struct pop_msg *msg;
-
- if (scb == NULLSCB) /* check for null -- wa6smn */
- return;
-
- if (scb->folder_len) /* add everything up */
- for (msg=scb->msg->next; msg!=NULL; msg=msg->next)
- if (!msg->deleted) {
- total += msg->len; ++count;
- }
- usprintf(scb->socket,stat_rsp,count,total);
- }
-
- static void near
- list_message(struct pop_scb *scb) {
- struct pop_msg *msg;
- int msg_no=0;
- long total=0;
- struct pop_msg *near goto_msg(struct pop_scb *,int );
-
- if (scb == NULLSCB) /* check for null -- wa6smn */
- return;
- if (scb->buf[sizeof(list_cmd) - 1] == ' ') {
- msg_no = atoi(&(scb->buf[sizeof(list_cmd) - 1]));
- msg=goto_msg(scb,msg_no);
- if (msg==NULL || msg->deleted)
- state_error(scb,"non existent or deleted message");
- else
- usprintf(scb->socket,list_single_rsp,msg_no,msg->len);
- } else {
- /* multiline */
- if (scb->folder_len) /* add everything */
- for (msg=scb->msg->next; msg!=NULL;msg=msg->next)
- if (!msg->deleted)
- total += msg->len,++msg_no;
-
- usprintf(scb->socket,list_multi_rsp,msg_no,total);
-
- if (scb->folder_len)
- for (msg=scb->msg->next,msg_no=1; msg!=NULL;msg=msg->next,msg_no++)
- if (!msg->deleted) {
- usprintf(scb->socket,"%d %ld\n",msg_no,msg->len);
- }
- usputs(scb->socket,multi_end_rsp);
- }
- }
-
- static void near
- retr_message(struct pop_scb *scb) {
- char *cp,line[BUF_LEN];
- long cnt;
- int msg_no;
- struct pop_msg *msg;
- struct pop_msg *near goto_msg(struct pop_scb *,int );
-
- if (scb == NULLSCB) /* check for null -- wa6smn */
- return;
-
- if (scb->buf[sizeof(retr_cmd) - 1] != ' ') {
- state_error(scb,"no such message");
- return;
- }
-
- msg_no = atoi(&(scb->buf[sizeof(retr_cmd) - 1]));
- msg=goto_msg(scb,msg_no);
- if (msg==NULL || msg->deleted) {
- state_error(scb,"no such message");
- return;
- }
-
- cnt = msg->len;
- usprintf(scb->socket,retr_rsp,cnt);
- fseek(scb->wf,msg->pos,SEEK_SET); /* Go there */
-
- while(!feof(scb->wf) && (cnt > 0)) {
- fgets(line,BUF_LEN,scb->wf);
- if((cp = strpbrk(line,"\r\n")) != NULLCHAR)
- *cp = '\0';
- if ( *line == '.' ) {
- usputc(scb->socket,'.');
- cnt--;
- }
- usputs(scb->socket,line);
- usputc(scb->socket,'\n');
- cnt -= (strlen(line)+2); /* Compensate for CRLF */
- pwait(NULL);
- }
-
- usputs(scb->socket,multi_end_rsp);
- if (msg_no >= scb->high_num)
- scb->high_num=msg_no; /* bump high water mark */
- }
-
- static void near
- noop_message(struct pop_scb *scb) {
- usprintf(scb->socket,noop_rsp);
- }
-
- static void near
- last_message(struct pop_scb *scb) {
- usprintf(scb->socket,last_rsp,scb->high_num);
- }
-
- static void near
- rset_message(struct pop_scb *scb) {
- struct pop_msg *msg;
- long total=0;
-
- if (scb->folder_len)
- for (msg=scb->msg->next; msg!=NULL; msg=msg->next)
- msg->deleted=FALSE,total+=msg->len;
-
- scb->high_num=0; /* reset last */
- scb->folder_modified=FALSE;
- usprintf(scb->socket,list_multi_rsp,scb->folder_len,total);
- }
-
- static void near
- top_message(struct pop_scb *scb) {
- char *ptr;
- char line[BUF_LEN];
- struct pop_msg *msg;
- int msg_no=0,lines=0;
- long total=0;
- struct pop_msg *near goto_msg(struct pop_scb *,int );
-
- if (scb == NULLSCB) /* check for null -- wa6smn */
- return;
-
- if (scb->buf[sizeof(top_cmd) - 1] != ' ') {
- state_error(scb,"No message specified");
- return;
- }
-
- for (ptr=scb->buf+sizeof(top_cmd); *ptr==' ' ; ++ptr);
- /* Space drop */
- for ( ; *ptr!=' ' && *ptr !='\0'; ++ptr);
- /* token drop */
- msg_no = atoi(&(scb->buf[sizeof(top_cmd) - 1]));
- lines = atoi(++ptr); /* Get # lines to top */
- if (lines < 0) lines=0;
-
- msg=goto_msg(scb,msg_no);
- if (msg==NULL || msg->deleted)
- {
- state_error(scb,"non existent or deleted message");
- return;
- }
- fseek(scb->wf,msg->pos,SEEK_SET); /* Go there */
- total=msg->len; /* Length of current message */
- usputs(scb->socket,noop_rsp); /* Give OK */
- do {
- fgets(line,BUF_LEN,scb->wf);
- if((ptr = strpbrk(line,"\r\n")) != NULLCHAR)
- *ptr = '\0';
- if ( *line == '.' ) {
- usputc(scb->socket,'.');
- total--;
- }
- total -= strlen(line)+2;
- usputs(scb->socket,line);
- usputc(scb->socket,'\n');
- } while (*line!='\0' && total>0);
-
- for ( ; total > 0 && lines; --lines) {
- fgets(line,BUF_LEN,scb->wf);
- if((ptr = strpbrk(line,"\r\n")) != NULLCHAR)
- *ptr = '\0';
- if ( *line == '.' ) {
- usputc(scb->socket,'.');
- total--;
- }
- total -= strlen(line)+2;
- usputs(scb->socket,line);
- usputc(scb->socket,'\n');
- }
- usprintf(scb->socket,multi_end_rsp);
- }
-
- static int near
- poplogin(char *username,char *pass) {
- char buf[BUF_LEN], *cp, *cp1;
- FILE *fp;
-
- if((fp = fopen(Popusers,READ_TEXT)) == NULLFILE) {
- /* User file doesn't exist */
- return(FALSE);
- }
-
- while(fgets(buf,sizeof(buf),fp),!feof(fp)) {
- if(buf[0] == '#')
- continue; /* Comment */
-
- if((cp = strchr(buf,':')) == NULLCHAR)
- continue; /* Bogus entry */
-
- *cp++ = '\0'; /* Now points to password */
- if(strcmp(username,buf) == 0)
- break; /* Found user name */
- }
-
- if(feof(fp)) {
- /* User name not found in file */
- fclose(fp);
- return(FALSE);
- }
- fclose(fp);
-
- if ((cp1 = strchr(cp,':')) == NULLCHAR)
- return(FALSE);
-
- *cp1 = '\0';
- if(strcmp(cp,pass) != 0) {
- /* Password required, but wrong one given */
- return(FALSE);
- }
-
- /* whew! finally made it!! */
- return(TRUE);
- }
-
- static void near
- dele_message(struct pop_scb *scb) {
- struct pop_msg *msg;
- int msg_no;
- struct pop_msg *near goto_msg(struct pop_scb *,int );
-
- if (scb == NULLSCB) /* check for null -- wa6smn */
- return;
- if (scb->buf[sizeof(retr_cmd) - 1] != ' ') {
- state_error(scb,"no such message");
- return;
- }
- msg_no = atoi(&(scb->buf[sizeof(retr_cmd) - 1]));
- msg=goto_msg(scb,msg_no);
- if (msg==NULL || msg->deleted) {
- state_error(scb,"attempt to access deleted message");
- return;
- }
- if (msg->deleted) /* Don't bother if already dead */ {
- state_error(scb,"message already deleted");
- return;
- }
- msg->deleted=TRUE;
- scb->folder_modified = TRUE;
- usprintf(scb->socket,dele_rsp,msg_no);
- }
-
-
- static int near
- newmail(struct pop_scb *scb) {
- char *folder_pathname;
- struct stat folder_stat;
- int s;
-
- folder_pathname = mxallocw(strlen(Mailspool) + strlen(scb->username) + 5);
- sprintf(folder_pathname,"%s/%s.txt",Mailspool,scb->username);
- s = stat(folder_pathname,&folder_stat);
- xfree(folder_pathname);
-
- if(s) {
- state_error(scb,"Unable to get old mail folder's status");
- return(FALSE);
- } else {
- return((folder_stat.st_size > scb->folder_file_size) ? TRUE : FALSE);
- }
- }
-
-
- static struct pop_msg * near
- goto_msg(struct pop_scb *scb,int msg_no)
- {
- int msg_num;
- struct pop_msg *msg;
-
- msg_num=msg_no-1;
- if (scb->folder_len==0 || msg_num < 0)
- return NULL;
- for (msg=scb->msg->next; msg_num && msg!=NULL; --msg_num) msg=msg->next;
- return msg;
- }
-
- #endif /* POP3_SERVER */
-